Skip to content

Stop restarting pgadmin on every lerd start - #1494

Merged
geodro merged 3 commits into
lerd-env:mainfrom
JoeJoeflyn:fix/1490-pgadmin-config-drift
Aug 14, 2026
Merged

Stop restarting pgadmin on every lerd start#1494
geodro merged 3 commits into
lerd-env:mainfrom
JoeJoeflyn:fix/1490-pgadmin-config-drift

Conversation

@JoeJoeflyn

Copy link
Copy Markdown
Contributor

RestartIfConfigDrifted rewrote a service's preset config files unconditionally and restarted the container when the newest file was newer than the container's boot. For pgadmin the pgpass file is rewritten on every pass even when its content has not changed, so its mtime advances past the container's start and the next lerd start sees it as drifted and restarts again, looping forever.

MaterializeServiceFilesChanged already compares content and skips the rewrite when nothing differs, so the mtime does not move. Route RestartIfConfigDrifted through it and bail out early when it reports no change.

Closes #1490

@JoeJoeflyn
JoeJoeflyn requested a review from a team as a code owner August 13, 2026 13:46
@geodro

geodro commented Aug 13, 2026

Copy link
Copy Markdown
Member

Appreciate you picking this one up, it's a genuinely annoying thing to chase. I dug into it on my machine though and I don't think it breaks the loop.

MaterializeServiceFiles is already a one line wrapper around MaterializeServiceFilesChanged, so the seam swap doesn't change what gets written, and the early return never fires for pgadmin because changed comes back true on every pass.

The reason is easy to miss from the outside. The pgpass mount is mode 0600 with chown true, so podman's :U re-owns it to a userns mapped uid on container start and the host user can't read it back afterwards. The content compare does an os.ReadFile, gets EACCES, and drops through to the unlink and rewrite branch, which sets changed. On my install the file sits there owned by 105049 at 0600 and cat gives permission denied, and re-materialising over it moves the mtime every time. So the loop is the file being unreadable, not the content differing, and you'd have to be looking right at it to spot that.

Flagging the test too, since it can't fail as written. It stubs the materialise seam to return false, a state pgadmin never actually produces, so it ends up asserting the patch instead of the bug and would stay green on a build that still restarts on every start.

The early return also costs a little even where it does work. The mtime is the durable signal and changed isn't, so if a pass rewrites a file and the restart then fails, the next pass bails before the mtime check ever runs and the container keeps serving stale config.

Whatever fixes this needs something we can still read after podman takes the file, probably a sidecar holding a hash of the last rendered content compared against the freshly rendered bytes. Happy for you to take another swing at it if you're up for it, otherwise I'll pick it up from here.

podman's :U flag re-owns the pgpass file to a userns-mapped
uid, so os.ReadFile EACCES and MaterializeServiceFilesChanged
rewrites it on every pass, moving the mtime past the
container's boot. The next lerd start sees it as drifted and
restarts again, looping forever.

Store a sha256 sidecar beside each materialised file. The
sidecar is never mounted into the container so podman never
re-owns it, and the host user can always read it back. When
ReadFile fails, compare the sidecar hash instead; if it
matches, skip the rewrite so the mtime does not move.
@JoeJoeflyn
JoeJoeflyn force-pushed the fix/1490-pgadmin-config-drift branch from 9960d11 to f796edb Compare August 14, 2026 08:23
@JoeJoeflyn

Copy link
Copy Markdown
Contributor Author

i updated with the sidecar hash approach you suggested

The hash is only needed for a mount podman re-owns through :U, where a restrictive mode leaves the file unreadable and the content compare cannot run at all. Writing one beside every preset file doubles the entries in a service's directory for nothing, so it now follows chown and is rewritten only when it actually moves.

Covers the other half of it too, a changed rendering still has to reach a file we cannot read back, and puts back the rationale the comments carried for comparing by content and for unlinking before the rewrite.
@geodro

geodro commented Aug 14, 2026

Copy link
Copy Markdown
Member

Verified this one on a real install with pgadmin reinstalled, and it holds. With the released binary the container restarted and pgpass got rewritten a couple of seconds after it, which is the loop. With your build the first start restarted once to clear the drift the old binary had just left behind, and the two starts after that moved nothing at all, container start time and pgpass mtime both frozen.

I pushed one commit on top rather than send you round again. The hash now follows chown, since only a mount podman re-owns can ever go unreadable and every other preset file was picking up a .sha256 next to it for nothing, and it is written only when it actually changes. I also added the other half of the test, a changed rendering still has to reach a file we cannot read back, and put the two comment blocks back where they explained why we compare by content and why the rewrite unlinks first.

Good instinct on the sidecar, it is the right shape for this. Thanks for sticking with it.

@JoeJoeflyn

Copy link
Copy Markdown
Contributor Author

thanks for the updated George

@geodro
geodro merged commit 39b4ecf into lerd-env:main Aug 14, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(services): pgadmin is restarted on every lerd start by a config-drift loop

2 participants